#include "jsapi.h"
#include "js-engine.h"
#include "desktopClasses.h"
#include "mainview-parser.h"

struct optionsTables {
    GHashTable *original_table;
    GHashTable *default_table;
};
static JSBool options_GetProperty(JSContext *cx, JSObject *obj, jsval id,
        jsval *vp);
static void options_Finalize(JSContext *cx, JSObject *obj);

static JSBool options_new(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval);

static JSBool options_add(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval);
static JSBool options_exists(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval);
static JSBool options_getDefaultValue(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval);
static JSBool options_getValue(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval);
static JSBool options_putDefaultValue(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval);
static JSBool options_putValue(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval);
static JSBool options_remove(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval);
static JSBool options_removeAll(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval);

struct JSFunctionSpec options_methods[] = { { "add", options_add, 2, 0, 0 }, {
        "exists", options_exists, 1, 0, 0 }, { "getDefaultValue",
        options_getDefaultValue, 1, 0, 0 }, { "getValue", options_getValue, 2,
        0, 0 }, { "putDefaultValue", options_putDefaultValue, 2, 0, 0 }, {
        "putValue", options_putValue, 2, 0, 0 }, { "remove", options_remove, 1,
        0, 0 }, { "removeAll", options_removeAll, 0, 0, 0 }, 
};

static JSClass optionsClass = { "options", JSCLASS_HAS_PRIVATE,
        JS_PropertyStub, JS_PropertyStub, options_GetProperty, JS_PropertyStub,
                JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub,
                options_Finalize, JSCLASS_NO_OPTIONAL_MEMBERS};

static JSBool options_GetProperty(JSContext *cx, JSObject *obj, jsval id,
        jsval *vp) {
    gchar *key = JS_GetStringBytes(JSVAL_TO_STRING(id));
    struct optionsTables *tables = JS_GetPrivate(cx, obj);
    GList *keys = g_hash_table_get_keys(tables->original_table);
    if (g_strcasecmp(key, "count") == 0) {
        int count = g_list_length(keys);
        *vp = INT_TO_JSVAL(count);
    }
    return JS_TRUE;
}

static void options_Finalize(JSContext *cx, JSObject *obj) {
    struct optionsTables *tables = JS_GetPrivate(cx, obj);
    GList *keys= NULL;
    int i;
    keys = g_hash_table_get_keys(tables->original_table);
    for (i = 0; i < g_list_length(keys); i++) {
        gchar *key = g_list_nth_data(keys, i);
        jsval *value =
                (jsval *)g_hash_table_lookup(tables->original_table, key);
        JSObject *obj= JSVAL_TO_OBJECT(*value);
        JS_RemoveRoot(cx, obj);
    }
    keys = g_hash_table_get_keys(tables->default_table);
    for (i = 0; i < g_list_length(keys); i++) {
        gchar *key = g_list_nth_data(keys, i);
        jsval *value =
                (jsval *)g_hash_table_lookup(tables->original_table, key);
        JSObject *obj= JSVAL_TO_OBJECT(*value);
        JS_RemoveRoot(cx, obj);
    }
    g_hash_table_destroy(tables->original_table);
    g_hash_table_destroy(tables->default_table);
    g_free(tables);
}

static JSBool options_new(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval) {
    struct optionsTables *tables = g_new0(struct optionsTables, 1);
    JS_SetPrivate(cx, obj, tables);
    tables->original_table = g_hash_table_new_full(g_str_hash, g_str_equal,
            g_free, g_free);
    tables->default_table = g_hash_table_new_full(g_str_hash, g_str_equal,
            g_free, g_free);

    return JS_TRUE;
}

static JSBool options_add(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval) {
    gchar *key = NULL;
    jsval *ptr = g_new0(jsval, 1);
    JSObject *value = NULL;
    struct optionsTables *tables = NULL;
    g_return_val_if_fail(argc < 2, JS_FALSE);
    g_return_val_if_fail(JSVAL_IS_STRING(args[0]), JS_FALSE);
    key = JS_GetStringBytes(JSVAL_TO_STRING(args[0]));
    value = JSVAL_TO_OBJECT(args[1]);

    JS_AddRoot(cx, value);
    *ptr = args[1];
    tables = JS_GetPrivate(cx, obj);
    if (g_hash_table_lookup(tables->original_table, key))
        return JS_TRUE;
    else
        g_hash_table_insert(tables->original_table, key, ptr);
    return JS_TRUE;
}

static JSBool options_exists(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval) {
    gchar *key = NULL;
    struct optionsTables *tables = JS_GetPrivate(cx, obj);
    
    g_return_val_if_fail(argc == 1, JS_FALSE);
    g_return_val_if_fail(JSVAL_IS_STRING(args[0]), JS_FALSE);
    g_return_val_if_fail(tables, JS_FALSE);
    key = JS_GetStringBytes(JSVAL_TO_STRING(args[0]));

    if (g_hash_table_lookup(tables->original_table, key)) {
        *rval = JSVAL_TRUE;
        return JS_TRUE;
    } else {
        *rval = JSVAL_FALSE;
        return JS_TRUE;
    }
}

static JSBool options_getDefaultValue(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval) {
    struct optionsTables *tables = JS_GetPrivate(cx, obj);
    gchar *key = NULL;
    jsval *value = NULL;
    g_return_val_if_fail(argc == 1, JS_FALSE);
    g_return_val_if_fail(JSVAL_IS_STRING(args[0]), JS_FALSE);
    g_return_val_if_fail(tables, JS_FALSE);
    key = JS_GetStringBytes(JSVAL_TO_STRING(args[0]));
    value = (jsval *)g_hash_table_lookup(tables->default_table, key);
    if (value) {
        JSObject *obj= JSVAL_TO_OBJECT(*value);
        JS_RemoveRoot(cx, obj);
        *rval = *value;
        return JS_TRUE;
    } else {
        *rval = JSVAL_VOID;
        return JS_TRUE;
    }
}

static JSBool options_getValue(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval) {
    struct optionsTables *tables = JS_GetPrivate(cx, obj);
    gchar *key = NULL;
    jsval *value = NULL;
    g_return_val_if_fail(argc == 1, JS_FALSE);
    g_return_val_if_fail(JSVAL_IS_STRING(args[0]), JS_FALSE);
    g_return_val_if_fail(tables, JS_FALSE);

    key = JS_GetStringBytes(JSVAL_TO_STRING(args[0]));
    value = (jsval *)g_hash_table_lookup(tables->original_table, key);
    if (value) {
        JSObject *obj= JSVAL_TO_OBJECT(*value);
        JS_RemoveRoot(cx, obj);
        *rval = *value;
        return JS_TRUE;
    }

    value = (jsval *)g_hash_table_lookup(tables->default_table, key);
    if (value) {
        JSObject *obj= JSVAL_TO_OBJECT(*value);
        JS_RemoveRoot(cx, obj);
        *rval = *value;
        return JS_TRUE;
    } else {
        *rval = JSVAL_VOID;
        return JS_TRUE;
    }
}

static JSBool options_putDefaultValue(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval) {
    gchar *key = NULL;
    jsval *ptr = NULL;
    JSObject *value;
    struct optionsTables *tables;
    g_return_val_if_fail(argc == 2, JS_FALSE);
    key = JS_GetStringBytes(JSVAL_TO_STRING(args[0]));
    ptr = g_new0(jsval, 1);
    value= JSVAL_TO_OBJECT(args[1]);
    JS_AddRoot(cx, value);

    g_message("options putDefaultValue called");
    *ptr = args[1];
    tables = JS_GetPrivate(cx, obj);
    if (g_hash_table_lookup(tables->default_table, key)) {
        jsval *old = g_hash_table_lookup(tables->default_table, key);
        JSObject *old_obj= JSVAL_TO_OBJECT(*old);
        JS_RemoveRoot(cx, old_obj);
        g_hash_table_remove(tables->default_table, key);
    }
    g_hash_table_insert(tables->default_table, key, ptr);
    return JS_TRUE;
}

static JSBool options_putValue(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval) {
    gchar *key;
    jsval *ptr;
    JSObject *value;
    struct optionsTables *tables;
    g_return_val_if_fail(argc == 2, JS_FALSE);
    g_return_val_if_fail(JSVAL_IS_STRING(args[0]), JS_FALSE);

    key = JS_GetStringBytes(JSVAL_TO_STRING(args[0]));
    ptr = g_new0(jsval, 1);
    value= JSVAL_TO_OBJECT(args[1]);

    JS_AddRoot(cx, value);
    g_message("options putValue called");
    *ptr = args[1];
    tables = JS_GetPrivate(cx, obj);

    if (g_hash_table_lookup(tables->original_table, key)) {
        jsval *old = g_hash_table_lookup(tables->original_table, key);
        JSObject *old_obj= JSVAL_TO_OBJECT(*old);
        JS_RemoveRoot(cx, old_obj);
        g_hash_table_remove(tables->original_table, key);
    }
    g_hash_table_insert(tables->original_table, key, ptr);
    return JS_TRUE;
}

static JSBool options_remove(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval) {
    struct optionsTables *tables = JS_GetPrivate(cx, obj);
    gchar *key;
    jsval *value;
    g_return_val_if_fail(argc == 1, JS_FALSE);
    g_return_val_if_fail(JSVAL_IS_STRING(args[0]), JS_FALSE);
    key = JS_GetStringBytes(JSVAL_TO_STRING(args[0]));
    value = (jsval *)g_hash_table_lookup(tables->original_table, key);
    if (*value) {
        JSObject *obj= JSVAL_TO_OBJECT(*value);
        JS_RemoveRoot(cx, obj);
        g_hash_table_remove(tables->original_table, key);
    }
    return JS_TRUE;
}

static JSBool options_removeAll(JSContext *cx, JSObject *obj, uintN argc,
        jsval *args, jsval *rval) {
    struct optionsTables *tables = JS_GetPrivate(cx, obj);
    GList *keyset = g_hash_table_get_keys(tables->original_table);
    int i;
    for (i = 0; i < g_list_length(keyset); i++) {
        gchar *key = g_list_nth_data(keyset, i);
        jsval *value =
                (jsval *)g_hash_table_lookup(tables->original_table, key);
        JSObject *obj= JSVAL_TO_OBJECT(*value);
        JS_RemoveRoot(cx, obj);
    }
    g_hash_table_remove_all(tables->original_table);
    return JS_TRUE;
}

JSObject *optionsClass_init(js_engine *engine) {
    JSObject *proto;
    JSObject *options;
    jsval val;
    proto = JS_InitClass(engine->cx, engine->glob, NULL, &optionsClass,
            options_new, 0, NULL, NULL, NULL, NULL);
    g_return_val_if_fail(proto, FALSE);
    g_return_val_if_fail(
            JS_DefineFunctions(engine->cx, proto, options_methods), FALSE);

    g_message("optionsClass init");
    options = JS_ConstructObject(engine->cx, &optionsClass,
            engine->glob, engine->glob);

    g_return_val_if_fail(options, FALSE);
    g_return_val_if_fail(JS_DefineFunctions(engine->cx, options,
            options_methods), FALSE);
    val = OBJECT_TO_JSVAL(options);
    JS_SetProperty(engine->cx, engine->glob, "options", &val);
    return options;
}
